package com.wordpress.standal.multimethod.api.class2

import scala.collection.mutable.ListBuffer

class MultiMethod [C1 <: AnyRef, C2 <: AnyRef, C3, R] {
  
  private class Entry(val cls1: Class[_ <: C1], val cls2: Class[_ <: C2], val func: Function3[C1, C2, C3, R])
  
  private val entries: ListBuffer[Entry] = new ListBuffer()
    
  def add[S1 <: C1, S2 <: C2](cls1: Class[S1], cls2: Class[S2], func: Function3[S1, S2, C3, R]) {
    entries += new Entry(cls1, cls2, new Function3[C1, C2, C3, R]() {
      def apply(c1: C1, c2: C2, c3: C3) = func.apply(c1.asInstanceOf[S1], c2.asInstanceOf[S2], c3)
    })
  }
  
  def apply(c1: C1, c2: C2, c3: C3) : R = {
    var current: Entry = null;
    for (e: Entry <- entries) {
      if (e.cls1.isAssignableFrom(c1.getClass) && e.cls2.isAssignableFrom(c2.getClass)) {
        if (current == null) {
          current = e
        } 
        else {
          if (e.cls1 == current.cls1) {
            if (current.cls2.isAssignableFrom(e.cls2)) {
              current = e
            }
          } 
          else if (current.cls1.isAssignableFrom(e.cls1)) {
            current = e
          }
        }
      }
    }
    current.func.apply(c1, c2, c3)
  }

}
object MultiMethod {
  
  class MultiMethod0 [C1 <: AnyRef, C2 <: AnyRef, R] {
    val rep = new MultiMethod[C1, C2, Unit, R]
    def apply(c1 : C1, c2: C2) = {
      rep.apply(c1, c2, null)
    }
    def add[S1 <: C1, S2 <: C2](cls1: Class[S1], cls2: Class[S2], func: Function2[S1, S2, R]) {
      rep.add(cls1, cls2, new Function3[S1, S2, Unit, R]() {
        def apply(c1 : S1, c2: S2, u : Unit) = func.apply(c1, c2)
      })
    }
  }

  class MultiMethod2 [C1 <: AnyRef, C2 <: AnyRef, C3, C4, R] {
    val rep = new MultiMethod[C1, C2, (C3, C4), R]
    def apply(c1 : C1, c2: C2, c3: C3, c4: C4) = {
      rep.apply(c1, c2, (c3, c4))
    }
    def add[S1 <: C1, S2 <: C2](cls1: Class[S1], cls2: Class[S2], func: Function4[S1, S2, C3, C4, R]) {
      rep.add(cls1, cls2, new Function3[S1, S2, (C3, C4), R]() {
        def apply(c1 : S1, c2: S2, c3: (C3, C4)) = func.apply(c1, c2, c3._1, c3._2)
      })
    }
  }

  def apply[C1 <: AnyRef, C2 <: AnyRef, R]() = {
    new MultiMethod0[C1, C2, R]()
  }
  
  def apply[C1 <: AnyRef, C2 <: AnyRef, C3, R]() = {
    new MultiMethod[C1, C2, C3, R]()
  }

  def apply[C1 <: AnyRef, C2 <: AnyRef, C3, C4, R]() = {
    new MultiMethod2[C1, C2, C3, C4, R]()
  }

}
